<template>
  <div class="flow-bg">
    <div class="flow-header">
      <van-icon name="arrow-left" class="flow-back" @click="goBack" />
      流程详情
    </div>
    <div class="flow-main">
      <van-loading
        v-if="loading"
        type="spinner"
        color="#1677ff"
        size="40px"
        vertical
        class="page-loading"
        >加载中...</van-loading
      >
      <div class="flow-title-row">
        <div class="flow-title-main">
          <span class="flow-title">{{ processDefinitionName }}</span>
          <!-- <span class="flow-status">{{
            flowStatus === "ongoing" ? "进行中" : flowStatus
          }}</span> -->
          <van-tag type="primary" v-if="flowStatus === 'ongoing'">进行中</van-tag>
          <van-tag type="primary" v-if="flowStatus == 'complete'">审批完成</van-tag>
          <van-tag type="primary" v-if="flowStatus == 'rejected'">审批驳回</van-tag>
          <van-tag type="primary" v-if="flowStatus == 'revoke'">审批撤销</van-tag>
        </div>
        <div class="flow-title-sub">
          <span class="flow-no">编号：{{ processInstanceId }}</span>
          <van-icon name="ellipsis" class="flow-title-more" />
        </div>
      </div>
      <div class="flow-user-row">
        <img
          class="flow-avatar"
          :src="flowInfo.avatar"
          @click="goToChat(flowInfo)"
        />
        <div class="flow-user-info">
          <div class="flow-user-meta">
            <span class="flow-user-name">{{ flowInfo.name }}</span>
            <span class="flow-time">提交于：{{ flowInfo.time }}</span>
          </div>
        </div>
      </div>
      <div class="flow-tabs">
        <div
          :class="['flow-tab', tab === 'form' && 'active']"
          @click="tab = 'form'"
        >
          表单数据
        </div>
        <div
          :class="['flow-tab', tab === 'record' && 'active']"
          @click="tab = 'record'"
        >
          流转记录
        </div>
      </div>

      <div v-if="tab === 'form'" class="flow-tab-panel">
        <phoneForm
          v-show="tab == 'form' && formItems && formItems.length"
          ref="PhoneForm"
          :formList.sync="formItems"
          :formConf="formAttrs"
          :form="formData"
          onlyShow
        >
        </phoneForm>
      </div>
      <div v-if="tab === 'record'" class="flow-tab-panel">
        <div class="timeline-list">
          <div
            v-for="(node, i) in flowNodes"
            :key="i"
            class="timeline-row"
            :ref="(el) => setTimelineRowRef(el, i)"
          >
            <div class="timeline-axis">
              <div
                class="timeline-dot"
                :style="{ background: node.color || '#1677ff' }"
                :ref="(el) => setTimelineDotRef(el, i)"
              >
                <span class="timeline-dot-text">{{ node.nodeName }}</span>
              </div>
              <div v-if="i < flowNodes.length - 1" class="timeline-line"></div>
            </div>
            <div class="timeline-card">
              <div class="timeline-card-header">
                <span class="timeline-role">{{ node.nodeName }}</span>
                <span class="timeline-time">{{ node.startTime }}</span>
              </div>
              <div class="timeline-card-main">
                <div class="timeline-users">
                  <div
                    v-for="user in node.approvals"
                    :key="user.id"
                    class="timeline-user"
                  >
                    <img
                      class="timeline-avatar"
                      :src="user.avatar"
                      @click="goToChat(user)"
                    />
                    <span class="timeline-username">{{ user.name }}</span>
                  </div>
                </div>
                <div
                  v-if="node.comments && node.comments.length"
                  class="timeline-comments"
                >
                  <div
                    v-for="(c, ci) in node.comments"
                    :key="ci"
                    class="timeline-comment"
                  >
                    <span class="timeline-comment-user"
                      >{{ c.user
                      }}<span v-if="c.status"> &gt; {{ c.status }}</span></span
                    >
                    <span class="timeline-comment-time">{{ c.time }}</span>
                    <div class="timeline-comment-bubble">{{ c.text }}</div>
                  </div>
                </div>
              </div>
            </div>
          </div>
        </div>
      </div>
    </div>
    <div class="flow-bottom-bar">
      <div class="flow-bar-btn" @click="onTransfer">转交</div>
      <div class="flow-bar-btn" @click="onBack">回退</div>
      <div class="flow-bar-btn">后加签</div>
      <div class="flow-bar-btn">更多</div>
      <div class="flow-bar-btn refuse">拒绝</div>
      <div class="flow-bar-btn agree" @click="onApprove">同意</div>
    </div>
    <!-- 审批弹窗 -->
    <van-popup
      v-model="showApproveDialog"
      round
      position="bottom"
      :style="{ height: 'auto', minHeight: '420px' }"
    >
      <div class="approve-dialog">
        <div class="approve-title">{{ approveTypeTitle }}</div>
        <!-- 转交专属 -->
        <div v-if="approveType === 'transfer'" class="approve-section">
          <div class="approve-label">选择人员</div>
          <div class="approve-select">
            <div class="selected-users">
              <div
                v-for="user in selectedUsersShow"
                :key="user.id"
                class="user-chip"
                @click.stop
              >
                <img :src="user.avatar || defaultAvatar" class="user-avatar" />
                <span class="user-name">{{ user.name }}</span>
                <span class="user-remove" @click.stop="removeUser(user)"
                  >×</span
                >
              </div>
              <div
                v-if="selectedUsers.length > 2"
                class="user-chip more-chip"
                @click.stop="showAllUsers"
              >
                +{{ selectedUsers.length - 2 }}
              </div>
              <span class="approve-plus" @click="openUserSelect"
                ><van-icon name="plus"
              /></span>
              <span v-if="!selectedUsers.length" class="approve-placeholder"
                >请设置要转交给谁</span
              >
            </div>
          </div>
        </div>
        <!-- 回退专属 -->
        <div v-if="approveType === 'back'" class="approve-section">
          <div class="approve-label">回退节点</div>
          <div class="approve-select" @click="openBackNodeSelect">
            <span v-if="selectedBackNode" class="approve-selected">{{
              selectedBackNode.nodeName
            }}</span>
            <span v-else class="approve-placeholder">选择想回退的节点</span>
            <van-icon name="arrow" />
          </div>
        </div>
        <!-- 处理意见通用 -->
        <div class="approve-section">
          <div class="approve-label">处理意见</div>
          <van-field
            v-model="approveText"
            rows="2"
            autosize
            type="textarea"
            maxlength="255"
            placeholder="请输入处理意见"
            show-word-limit
          />
          <div class="quick-reply">
            <span
              v-for="(txt, i) in quickReplies"
              :key="i"
              class="quick-reply-btn"
              @click="onQuickReply(txt)"
            >
              {{ txt }}
            </span>
          </div>
        </div>

        <!-- 文件上传通用 -->

        <!-- <div class="approve-section">
          <van-uploader v-model="files" multiple :max-count="3" />
          <div class="approve-upload-tip">
            <van-icon name="cloud-upload-o" /> 文件上传
          </div>
        </div> -->

        <!-- 审批签字，仅同意时展示 -->
        <div v-if="approveType === 'agree'" class="approve-section">
          <div class="approve-label" style="margin-bottom: 0">审批签字</div>
          <!-- <div class="approve-sign-row">
            <span class="approve-link" @click="loadLastSign">加载上次签名</span>
            <van-checkbox v-model="saveSign">保存本次签名</van-checkbox>
          </div> -->
          <van-button
            class="approve-sign-btn"
            icon="edit"
            type="primary"
            @click="openSign"
            >点击签字</van-button
          >
          <div v-if="signImg" class="sign-preview">
            <img :src="signImg" style="max-width: 100%; max-height: 80px" />
          </div>
        </div>
        <div class="approve-actions">
          <van-button block @click="onDialogCancel">取消</van-button>
          <van-button type="primary" block @click="onDialogConfirm"
            >确定</van-button
          >
        </div>
      </div>
    </van-popup>
    <!-- 选择人员全屏弹窗 -->
    <van-popup
      v-model="showUserSelect"
      position="right"
      style="width: 100vw; height: 100vh; z-index: 3000"
      :close-on-click-overlay="false"
    >
      <userSelect
        :selected="selectedUsers"
        @confirm="onUserSelectConfirm"
        @cancel="onUserSelectCancel"
      />
    </van-popup>
    <!-- 签名弹窗 -->
    <esignDialog ref="esignDialog" @update="onSignUpdate" />
    <!-- 回退节点选择弹窗 -->
    <van-popup
      v-model="showBackNodeSelect"
      position="bottom"
      :style="{ height: '50%' }"
      round
    >
      <div class="back-node-select">
        <div class="back-node-header">
          <span class="back-node-title">选择回退节点</span>
          <van-icon name="cross" @click="closeBackNodeSelect" />
        </div>
        <div class="back-node-list">
          <van-radio-group v-model="selectedBackNodeId">
            <div
              v-for="node in backNodes"
              :key="node.nodeId"
              class="back-node-item"
              @click="selectBackNode(node)"
            >
              <van-radio :name="node.nodeId" />
              <div class="back-node-info">
                <div class="back-node-name">{{ node.nodeName }}</div>
                <div class="back-node-meta">
                  <span v-if="node.userName" class="back-node-user">{{
                    node.userName
                  }}</span>
                  <span v-if="node.endTime" class="back-node-time">{{
                    node.endTime
                  }}</span>
                </div>
              </div>
            </div>
          </van-radio-group>
        </div>
        <div class="back-node-actions">
          <van-button block type="primary" @click="confirmBackNodeSelect"
            >确定</van-button
          >
        </div>
      </div>
    </van-popup>
  </div>
</template>

<script>
import { getInstanceInfo } from "@/api/api.js";
import "../../components/common/initPhone";

import {
  deepClone,
  setFormValue,
  arrayToObject,
} from "@/components/formGenerator/utils";
import phoneForm from "../../components/formGenerator/formComponents/phoneForm.vue";
import esignDialog from "@/components/formGenerator/formComponents/phoneComponents/components/esignDialog.vue";
import userSelect from "../userSelect.vue";
import { Toast } from "vant";

export default {
  name: "flowDetail",
  components: { phoneForm, esignDialog, userSelect },
  data() {
    return {
      tab: "form",
      flowInfo: {
        name: "",
        avatar: "",
        time: "",
      },
      flowNodes: [],
      processDefinitionName: "",
      flowStatus: "",
      processInstanceId: "",
      formItems: [],
      formAttrs: {},
      formData: {},
      timelineRowRefs: [],
      timelineDotRefs: [],
      taskId: "",
      // 审批弹窗相关
      showApproveDialog: false,
      approveText: "",
      quickReplies: [
        "朕准了",
        "我觉得可以喔",
        "OK",
        "同意",
        "好的",
        "牛而逼之",
        "达咩达咩🙅‍♂️",
      ],
      files: [],
      // 新增：签名、选人
      signImg: "",
      needSign: false,
      needSelectUser: false,
      selectedUsers: [],
      approveType: "agree", // agree/transfer/back
      approveTypeTitle: "同意",
      saveSign: true,
      backNode: "",
      showUserSelect: false,
      defaultAvatar: "", // 你可以换成自己的默认头像路径
      loading: false,
      backNodes: [],
      selectedBackNode: null,
      showBackNodeSelect: false,
      selectedBackNodeId: "",
    };
  },
  computed: {
    selectedUsersShow() {
      return this.selectedUsers.slice(0, 2);
    },
  },
  mounted() {
    this.processInstanceId = this.$route.query.processInstanceId;
    this.taskId = this.$route.query.taskId;
    this.fetchInstanceInfo();
    this.$nextTick(this.updateTimelineLines);
  },
  updated() {
    this.$nextTick(this.updateTimelineLines);
  },
  methods: {
    fetchInstanceInfo() {
      if (!this.processInstanceId) return;
      this.loading = true;
      getInstanceInfo({
        processInstanceId: this.processInstanceId,
        taskId: this.taskId,
      })
        .then((res) => {
          const data = res.data && res.data ? res.data : {};
          this.processDefinitionName = data.definitionName || "";
          this.flowStatus = data.flowStatus || "";
          this.processInstanceId = data.instanceId || "";
          this.flowInfo = {
            name: data.rootUser?.realName || "",
            avatar: data.rootUser?.avatar || "",
            time: data.startTime || "",
          };
          this.flowNodes = data.flowHisList || [];
          this.formItems = setFormValue(
            JSON.parse(deepClone(data.formItems) || []),
            data.formData
          );
          this.backNodes = data.backNodeList;
          // this.formItems = data.formItem ? JSON.parse(data.formItem) : [];
          // this.formData = data.formData || {};
        })
        .finally(() => {
          this.loading = false;
        });
    },
    goBack() {
      this.$router.back();
    },
    onBack() {
      this.approveType = "back";
      this.approveTypeTitle = "回退";
      this.needSign = false;
      this.needSelectUser = false;
      this.showApproveDialog = true;
    },
    setTimelineRowRef(el, i) {
      if (el) this.timelineRowRefs[i] = el;
    },
    setTimelineDotRef(el, i) {
      if (el) this.timelineDotRefs[i] = el;
    },
    updateTimelineLines() {
      for (let i = 0; i < this.timelineRowRefs.length - 1; i++) {
        const row = this.timelineRowRefs[i];
        const nextRow = this.timelineRowRefs[i + 1];
        const dot = this.timelineDotRefs[i];
        const nextDot = this.timelineDotRefs[i + 1];
        if (!row || !nextRow || !dot || !nextDot) continue;
        const line = row.querySelector(".timeline-line");
        if (!line) continue;
        const dotRect = dot.getBoundingClientRect();
        const nextDotRect = nextDot.getBoundingClientRect();
        const rowRect = row.getBoundingClientRect();
        const startY = dotRect.top + dotRect.height / 2 - rowRect.top;
        const endY = nextDotRect.top + nextDotRect.height / 2 - rowRect.top;
        const height = endY - startY;
        line.style.top = `${startY}px`;
        line.style.height = `${height}px`;
      }
    },
    onApprove() {
      this.approveType = "agree";
      this.approveTypeTitle = "同意";
      this.needSign = true;
      this.needSelectUser = false;
      this.showApproveDialog = true;
    },
    onTransfer() {
      this.approveType = "transfer";
      this.approveTypeTitle = "转交";
      this.needSign = false;
      this.needSelectUser = true;
      this.showApproveDialog = true;
    },
    openSign() {
      this.$refs.esignDialog && this.$refs.esignDialog.open();
    },
    onSignUpdate(img) {
      this.signImg = img;
    },
    openUserSelect() {
      this.showUserSelect = true;
    },
    onUserSelectConfirm(selected) {
      this.selectedUsers = selected;
      this.showUserSelect = false;
    },
    onUserSelectCancel() {
      this.showUserSelect = false;
    },
    onQuickReply(txt) {
      this.approveText = txt;
    },

    onDialogConfirm() {
      // TODO: 提交审批意见、附件、签名、选人
      // this.showApproveDialog = false;
      console.log("approveText", this.approveText);
      console.log("files", this.files);
      console.log("signImg", this.signImg);
      console.log("selectedUsers", this.selectedUsers);
      console.log("selectedBackNode", this.selectedBackNode);
      this.approveText = "";
      this.files = [];
      this.signImg = "";
      this.selectedUsers = [];
      this.selectedBackNode = null;
      this.selectedBackNodeId = "";
    },
    onDialogCancel() {
      this.showApproveDialog = false;
      this.selectedBackNode = null;
      this.selectedBackNodeId = "";
    },
    openBackNodeSelect() {
      this.showBackNodeSelect = true;
    },
    loadLastSign() {
      // TODO: 加载上次签名
    },
    removeUser(user) {
      this.selectedUsers = this.selectedUsers.filter((u) => u.id !== user.id);
    },
    showAllUsers() {
      this.$dialog.alert({
        title: "全部人员",
        message: this.selectedUsers.map((u) => u.name).join("、"),
      });
    },
    selectBackNode(node) {
      this.selectedBackNodeId = node.nodeId;
      this.selectedBackNode = node;
    },
    confirmBackNodeSelect() {
      if (!this.selectedBackNode) {
        Toast("请选择一个回退节点");
        return;
      }
      this.showBackNodeSelect = false;
    },
    closeBackNodeSelect() {
      this.showBackNodeSelect = false;
    },
    goToChat(user) {
      // 跳转到聊天页面
      this.$router.push({
        path: "/chat",
        query: {
          userId: user.id || user.name,
          userName: user.name || user.realName,
          userAvatar: user.avatar,
        },
      });
    },
  },
  activated() {
    // 页面激活时检查是否有选人结果
    if (this.$route.query.selected) {
      try {
        this.selectedUsers = JSON.parse(this.$route.query.selected);
      } catch (e) {}
    }
  },
};
</script>

<style scoped>
.flow-bg {
  /* min-height: 100vh; */
  background: #fff;
  display: flex;
  flex-direction: column;
  padding-bottom: 0;
}
.flow-header {
  background: #1677ff;
  color: #fff;
  font-size: 22px;
  font-weight: bold;
  text-align: center;
  padding: 18px 0 12px 0;
  letter-spacing: 2px;
  position: relative;
}
.flow-back {
  position: absolute;
  left: 18px;
  top: 18px;
  font-size: 22px;
  color: #fff;
  cursor: pointer;
}
.flow-main {
  padding: 0 16px;
  position: relative;
  min-height: 300px;
}
.flow-title-row {
  margin-top: 18px;
  margin-bottom: 10px;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  border-bottom: 1.5px solid #e6f7ff;
  padding-bottom: 8px;
  background: none;
}
.flow-title-main {
  display: flex;
  align-items: center;
  gap: 10px;
  min-width: 0;
  margin-bottom: 4px;
}
.flow-title-sub {
  display: flex;
  align-items: center;
  gap: 8px;
  color: #bbb;
  font-size: 13px;
  min-width: 0;
}
.flow-title {
  font-size: 20px;
  font-weight: 700;
  color: #222;
  max-width: 48vw;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  letter-spacing: 1px;
  margin-right: 2px;
}
.flow-status {
  background: linear-gradient(90deg, #42d0ea 0%, #1677ff 100%);
  color: #fff;
  font-size: 12px;
  border-radius: 10px;
  padding: 2px 10px;
  margin-left: 4px;
  font-weight: 600;
  box-shadow: 0 2px 8px #1677ff22;
  letter-spacing: 1px;
  display: inline-block;
  min-width: 38px;
  text-align: center;
  border: 1.2px solid #e0f7fa;
}
.flow-no {
  color: #bbb;
  font-size: 13px;
  font-weight: 400;
  letter-spacing: 0.5px;
}
.flow-title-more {
  font-size: 18px;
  color: #bbb;
  cursor: pointer;
  margin-left: 2px;
}
.flow-user-row {
  display: flex;
  align-items: center;
  margin-bottom: 2px;
}
.flow-avatar {
  width: 32px;
  height: 32px;
  border-radius: 50%;
  margin-right: 10px;
  object-fit: cover;
  background: #fff;
  border: 2px solid #fff;
  box-shadow: 0 1px 4px #1677ff22;
  cursor: pointer;
  transition: transform 0.2s, box-shadow 0.2s;
}

.flow-avatar:hover {
  transform: scale(1.1);
  box-shadow: 0 2px 8px #1677ff44;
}

.flow-avatar:active {
  transform: scale(0.95);
}

.flow-user-info {
  display: flex;
  flex-direction: column;
}
.flow-user-meta {
  display: flex;
  align-items: center;
  gap: 16px;
}
.flow-user-name {
  font-size: 15px;
  color: #222;
  font-weight: 500;
}
.flow-time {
  font-size: 13px;
  color: #888;
  margin-left: 0;
}
.flow-tabs {
  display: flex;
  border-bottom: 1.5px solid #f2f2f2;
  margin-bottom: 0;
  margin-top: 10px;
}
.flow-tab {
  flex: 1;
  text-align: center;
  font-size: 15px;
  color: #888;
  padding: 10px 0 8px 0;
  cursor: pointer;
  position: relative;
  font-weight: 500;
  transition: color 0.2s;
}
.flow-tab.active {
  color: #1677ff;
}
.flow-tab.active::after {
  content: "";
  display: block;
  position: absolute;
  left: 20%;
  right: 20%;
  bottom: -2px;
  height: 2.5px;
  background: #1677ff;
  border-radius: 2px;
}
.flow-tab-panel {
  padding: 18px 0 0 0;
  min-height: 120px;
}
.timeline-list {
  position: relative;
  margin-left: 0;
  /* padding-left: 18px; */
}
.timeline-row {
  display: flex;
  align-items: flex-start;
  position: relative;
  padding-bottom: 18px;
  min-height: 0;
}
.timeline-axis {
  width: 56px;
  position: relative;
  height: 100%;
  min-height: 0;
}
.timeline-dot {
  width: 40px;
  height: 40px;
  border-radius: 50%;
  color: #fff;
  font-size: 15px;
  font-weight: bold;
  box-shadow: 0 4px 16px #1677ff33, 0 1.5px 6px #42d0ea22;
  border: 3px solid #fff;
  letter-spacing: 1px;
  background: linear-gradient(135deg, #42d0ea 0%, #1677ff 100%);
  position: absolute;
  left: 50%;
  top: 0;
  transform: translateX(-50%);
  z-index: 2;
  transition: box-shadow 0.3s;
  display: flex;
  align-items: center;
  justify-content: center;
}
.timeline-row:first-child .timeline-dot {
  background: linear-gradient(135deg, #1677ff 0%, #42d0ea 100%);
  box-shadow: 0 0 0 4px #1677ff22, 0 4px 16px #1677ff33;
}
.timeline-dot[style*="#e0e0e0"] {
  background: #e0e0e0 !important;
  color: #888;
  box-shadow: 0 2px 8px #bbb2;
}
.timeline-dot[style*="#7db3ff"] {
  background: linear-gradient(135deg, #e6f7ff 0%, #7db3ff 100%) !important;
  color: #1677ff;
  box-shadow: 0 2px 8px #7db3ff33;
}
.timeline-dot.current {
  animation: breath 1.2s infinite alternate;
  box-shadow: 0 0 0 8px #42d0ea33, 0 4px 16px #1677ff33;
}
@keyframes breath {
  from {
    box-shadow: 0 0 0 8px #42d0ea33, 0 4px 16px #1677ff33;
  }
  to {
    box-shadow: 0 0 0 16px #42d0ea22, 0 4px 16px #1677ff33;
  }
}
.timeline-dot-text {
  display: block;
  max-width: 32px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.timeline-line {
  width: 2px;
  background: linear-gradient(to bottom, #e0e0e0 60%, #fff0 100%);
  position: absolute;
  left: 50%;
  top: 40px;
  bottom: 0;
  transform: translateX(-50%);
  z-index: 0;
  min-height: 24px;
}
.timeline-row:last-child .timeline-line {
  display: none;
}
.timeline-card {
  background: #fff;
  border-radius: 10px;
  box-shadow: 0 4px 18px #42d0ea11, 0 1.5px 6px #42d0ea11;
  border: 1.5px solid #b3e0ff;
  margin-bottom: 16px;
  margin-left: 8px;
  padding: 10px 10px 10px 10px;
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  justify-content: center;
  position: relative;
  background: #fafdff;
}
.timeline-card-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 4px;
}
.timeline-role {
  font-size: 15px;
  color: #1677ff;
  font-weight: 600;
  max-width: 60vw;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  display: flex;
  align-items: center;
  gap: 6px;
}
.timeline-role::before {
  content: "";
  display: inline-block;
  width: 18px;
  height: 18px;
  background: url('data:image/svg+xml;utf8,<svg fill="%231677ff" viewBox="0 0 24 24" width="18" height="18" xmlns="http://www.w3.org/2000/svg"><circle cx="12" cy="12" r="10" fill="%23e6f7ff"/><path d="M12 7a2 2 0 1 1 0 4 2 2 0 0 1 0-4zm0 6c-2.21 0-4 1.34-4 3v1h8v-1c0-1.66-1.79-3-4-3z" fill="%231677ff"/></svg>')
    no-repeat center/contain;
  margin-right: 4px;
  vertical-align: middle;
}
.timeline-time {
  font-size: 12px;
  color: #bbb;
  margin-left: 8px;
  white-space: nowrap;
  font-weight: 400;
}
.timeline-card-main {
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.timeline-users {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 2px;
  flex-wrap: wrap;
}
.timeline-user {
  display: flex;
  align-items: center;
  gap: 4px;
}
.timeline-avatar {
  width: 30px;
  height: 30px;
  border-radius: 50%;
  object-fit: cover;
  background: #f2f2f2;
  border: 2.5px solid #fff;
  box-shadow: 0 1px 6px #42d0ea33;
  background-image: linear-gradient(135deg, #42d0ea 0%, #e0f7fa 100%);
  cursor: pointer;
  transition: transform 0.2s, box-shadow 0.2s;
}

.timeline-avatar:hover {
  transform: scale(1.1);
  box-shadow: 0 2px 12px #42d0ea66;
}

.timeline-avatar:active {
  transform: scale(0.95);
}

.timeline-username {
  font-size: 14px;
  color: #333;
  font-weight: 400;
}
.timeline-comments {
  margin-top: 4px;
}
.timeline-comment {
  margin-bottom: 4px;
}
.timeline-comment-user {
  font-size: 13px;
  color: #888;
  margin-right: 6px;
}
.timeline-comment-time {
  font-size: 12px;
  color: #bbb;
  margin-right: 6px;
}
.timeline-comment-bubble {
  display: inline-block;
  background: #f6faff;
  color: #333;
  font-size: 14px;
  border-radius: 14px;
  padding: 8px 16px;
  margin-top: 2px;
  margin-bottom: 2px;
  box-shadow: 0 1px 6px #1677ff11;
}
.flow-bottom-bar {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  background: #fff;
  box-shadow: 0 -2px 12px #1677ff11;
  display: flex;
  flex-wrap: nowrap;
  padding: 0 4px 0 4px;
  z-index: 10;
  border-top: 1px solid #f2f2f2;
  min-height: 54px;
  align-items: center;
  justify-content: flex-start;
  gap: 0;
  box-sizing: border-box;
  width: 100vw;
  max-width: 100vw;
  overflow-x: auto;
  overflow-y: hidden;
  scrollbar-width: none;
}
.flow-bottom-bar::-webkit-scrollbar {
  display: none;
}
.flow-bar-btn {
  display: inline-block;
  margin: 0 4px;
  padding: 10px 18px;
  border-radius: 14px;
  font-size: 15px;
  color: #1677ff;
  background: #f6f8fa;
  font-weight: 500;
  cursor: pointer;
  transition: background 0.2s, color 0.2s;
  border: none;
  min-width: 60px;
  max-width: none;
  text-align: center;
  box-shadow: 0 1.5px 6px #1677ff11;
  white-space: nowrap;
}
.flow-bar-btn.refuse {
  color: #ff3b30;
  background: #fff0f0;
  border: 1px solid #ff3b30;
}
.flow-bar-btn.agree {
  color: #fff;
  background: #1677ff;
  border: 1px solid #1677ff;
}
@media (max-width: 600px) {
  .flow-bottom-bar {
    padding: 0 2px 0 2px;
  }
  .flow-bar-btn {
    font-size: 14px;
    padding: 9px 12px;
    border-radius: 12px;
    margin: 0 2px;
    min-width: 60px;
    max-width: none;
  }
}
.approve-dialog {
  border-radius: 18px 18px 0 0;
  background: #fff;
  padding: 18px 16px 12px 16px;
}
.approve-title {
  text-align: center;
  font-size: 20px;
  font-weight: bold;
  margin-bottom: 18px;
  letter-spacing: 2px;
}
.approve-section {
  margin-bottom: 18px;
}
.approve-label {
  font-size: 16px;
  font-weight: 600;
  margin-bottom: 8px;
  color: #222;
}
.approve-select {
  display: flex;
  align-items: center;
  gap: 8px;
  min-height: 36px;
  cursor: pointer;
}
.approve-plus {
  width: 32px;
  height: 32px;
  background: #1677ff;
  color: #fff;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 20px;
}
.approve-placeholder {
  color: #bbb;
  font-size: 15px;
}
.approve-selected {
  color: #1677ff;
  font-size: 15px;
}
.approve-upload-tip {
  display: flex;
  align-items: center;
  color: #1677ff;
  font-size: 15px;
  margin-top: 6px;
  gap: 4px;
}
.quick-reply {
  margin: 8px 0 12px 0;
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}
.quick-reply-btn {
  background: #f6f8fa;
  color: #1677ff;
  border-radius: 8px;
  padding: 4px 10px;
  font-size: 14px;
  cursor: pointer;
  border: 1px solid #e6f7ff;
}
.approve-actions {
  display: flex;
  gap: 12px;
  margin-top: 18px;
}
.approve-actions .van-button {
  flex: 1;
  border-radius: 12px;
}
.approve-sign-row {
  display: flex;
  align-items: center;
  gap: 18px;
  margin: 8px 0 10px 0;
}
.approve-link {
  color: #1677ff;
  font-size: 15px;
  cursor: pointer;
  margin-right: 8px;
}
.approve-sign-btn {
  margin-bottom: 8px;
  width: 140px;
  font-size: 15px;
  display: flex;
  align-items: center;
  justify-content: center;
}
.sign-preview {
  margin: 8px 0;
  text-align: center;
}
.user-preview {
  margin: 8px 0;
  font-size: 14px;
  color: #1677ff;
}
.selected-users {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  align-items: center;
  min-height: 36px;
}
.user-chip {
  display: flex;
  align-items: center;
  background: #f0f6ff;
  border-radius: 16px;
  padding: 2px 8px 2px 2px;
  position: relative;
  margin-right: 4px;
}
.user-avatar {
  width: 24px;
  height: 24px;
  border-radius: 50%;
  object-fit: cover;
  margin-right: 4px;
  background: #fff;
  border: 1px solid #e6e6e6;
}
.user-name {
  font-size: 14px;
  color: #1677ff;
  margin-right: 4px;
}
.user-remove {
  color: #bbb;
  font-size: 16px;
  cursor: pointer;
  margin-left: 2px;
}
.more-chip {
  background: #e6f0ff;
  color: #1677ff;
  font-weight: bold;
  cursor: pointer;
}
.approve-plus {
  margin-left: 4px;
  color: #1677ff;
  font-size: 20px;
  cursor: pointer;
}
.approve-placeholder {
  color: #bbb;
  margin-left: 8px;
}
.page-loading {
  position: absolute;
  left: 0;
  top: 0;
  width: 100%;
  height: 100%;
  background: rgba(255, 255, 255, 0.7);
  z-index: 100;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 16px;
}
.back-node-select {
  border-radius: 18px 18px 0 0;
  background: #fff;
  padding: 18px 16px 12px 16px;
  height: 100%;
  display: flex;
  flex-direction: column;
}
.back-node-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 18px;
  padding-bottom: 12px;
  border-bottom: 1px solid #f2f2f2;
}
.back-node-title {
  font-size: 18px;
  font-weight: 600;
  color: #222;
}
.back-node-header .van-icon {
  font-size: 20px;
  color: #999;
  cursor: pointer;
  padding: 4px;
}
.back-node-list {
  flex: 1;
  overflow-y: auto;
  margin-bottom: 18px;
}
.back-node-item {
  display: flex;
  align-items: center;
  padding: 12px 0;
  border-bottom: 1px solid #f8f8f8;
  cursor: pointer;
  transition: background-color 0.2s;
}
.back-node-item:hover {
  background-color: #f8f9fa;
}
.back-node-item:last-child {
  border-bottom: none;
}
.back-node-item .van-radio {
  margin-right: 12px;
}
.back-node-info {
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.back-node-name {
  font-size: 15px;
  color: #222;
  font-weight: 500;
}
.back-node-meta {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
}
.back-node-user {
  font-size: 13px;
  color: #666;
  background: #f0f0f0;
  padding: 2px 8px;
  border-radius: 10px;
}
.back-node-time {
  font-size: 12px;
  color: #999;
}
.back-node-actions {
  padding-top: 12px;
  border-top: 1px solid #f2f2f2;
}
.back-node-actions .van-button {
  border-radius: 12px;
  height: 44px;
  font-size: 16px;
  font-weight: 500;
}
</style> 